package com.xq.kotlin.coroutines

import kotlinx.coroutines.*

/**
 * @author aidenquan
 *         Created by junlintianxia on 2021/07/26.
 *         1、协程内部异常处理流程：launch 会在内部出现未捕获的异常时尝试触发对父协程的取消，能否取消要看作用域的定义，
 *         如果取消成功，那么异常传递给父协程，否则传递给启动时上下文中配置的 CoroutineExceptionHandler 中，如果没有配置，
 *         会查找全局（JVM上）的 CoroutineExceptionHandler 进行处理，如果仍然没有，那么就将异常交给当前线程的
 *         UncaughtExceptionHandler 处理；而 async 则在未捕获的异常出现时同样会尝试取消父协程，
 *         但不管是否能够取消成功都不会后其他后续的异常处理，直到用户主动调用 await 时将异常抛出。
 *         2、异常在作用域内的传播：当协程出现异常时，会根据当前作用域触发异常传递，GlobalScope 会创建一个独立的作用域，所谓“自成一派”，
 *         而 在 coroutineScope 当中协程异常会触发父协程的取消，进而将整个协程作用域取消掉，
 *         如果对 coroutineScope 整体进行捕获，也可以捕获到该异常，所谓“一损俱损”；
 *         如果是 supervisorScope，那么子协程的异常不会向上传递，所谓“自作自受”。
 *         3、join 和 await 的不同：join 只关心协程是否执行完，await 则关心运行的结果，因此 join 在协程出现异常时也不会抛出该异常，
 *         而 await 则会；考虑到作用域的问题，如果协程抛异常，可能会导致父协程的取消，因此调用 join 时尽管不会对协程本身的异常进行抛出，
 *         但如果 join 调用所在的协程被取消，那么它会抛出取消异常，这一点需要留意。
 */

fun main() = runBlocking {
    supervisorScope {
        handleException1()
        handleException2()
        handleException3()
        handleException4()
    }

}


inline fun log(message: Any?) {
    println("${Thread.currentThread()} $message")
}

private suspend fun handleException1() {
    val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
        log("Throws an exception with message: ${throwable.message}")
    }

    log(1)
    GlobalScope.launch(exceptionHandler) {
        withTimeout(1300L) {
            repeat(1000) { i ->
                log("I'm sleeping $i ...")
                delay(500L)
                throw Exception("Hey!")
            }
        }
    }.join()
    log(2)
}

/**
 * 没有明确指出，它是遵循默认的作用于规则的，也就是 coroutineScope 的规则了，出现未捕获的异常会尝试传递给父协程并尝试取消父协程。
 * 子协程间相互影响
 */
private suspend fun handleException2() {
    log("handleException2------------")
    log(1)
    try {
        coroutineScope {//①
            log(2)
            launch(Dispatchers.IO) { // ②
                log(3)
                launch { // ③
                    log(4)
                    delay(100)
                    throw ArithmeticException("Hey!!")
                }
                log(5)
            }
            log(6)
            val job = launch(Dispatchers.IO) { // ④
                log(7)
                delay(1000)
            }
            try {
                log(8)
                job.join()
                log(9)
            } catch (e: Exception) {
                log("10. $e")
            }
        }
        log(11)
    } catch (e: Exception) {
        log("12.$e")
    }
    log(13)
    log("------------handleException2")
}

/**
 * 明确要求子协程之间相互独立不干扰时，使用 supervisorScope
 */
private suspend fun handleException3() {
    log("handleException3------------")
    log(1)
    try {
        supervisorScope {//①
            log(2)
            val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
                log("${coroutineContext[CoroutineName]} $throwable")
            }
            launch(Dispatchers.IO + exceptionHandler + CoroutineName("②")) { // ②
                log(3)
                launch { // ③
                    log(4)
                    delay(100)
                    throw ArithmeticException("Hey!!")
                }
                log(5)
            }
            log(6)
            val job = launch(Dispatchers.IO) { // ④
                log(7)
                delay(1000)
            }
            try {
                log(8)
                job.join()
                log(9)
            } catch (e: Exception) {
                log("10. $e")
            }
        }
        log(11)
    } catch (e: Exception) {
        log("12.$e")
    }
    log(13)
    log("------------handleException3")
}

private suspend fun handleException4() {
    log("handleException4------------")
    log(1)
    try {
        coroutineScope {//①
            log(2)
            withContext(Dispatchers.IO) { // ②
                log(3)
                launch { // ③
                    log(4)
                    delay(100)
                    throw ArithmeticException("Hey!!")
                }
                log(5)
            }
            log(6)
            val job = launch(Dispatchers.IO) { // ④
                log(7)
                delay(1000)
            }
            try {
                log(8)
                job.join()
                log(9)
            } catch (e: Exception) {
                log("10. $e")
            }
        }
        log(11)
    } catch (e: Exception) {
        log("12.$e")
    }
    log(13)
    log("------------handleException4")
}